<?php
/**
 * Created by PhpStorm.
 * User: kwibon
 * Date: 2018/12/24
 * Time: 16:27
 */

namespace app\admin\model;

use think\Db;
use \think\Model;

class GeoHash
{
    public static $table = "0123456789bcdefghjkmnpqrstuvwxyz";
    public static $bits = array(
        0b10000, 0b01000, 0b00100, 0b00010, 0b00001
    );

    public static function encode($lng, $lat, $prec = 0.00001)
    {
        $minlng = -180;
        $maxlng = 180;
        $minlat = -90;
        $maxlat = 90;
        $hash = array();
        $error = 180;
        $isEven = true;
        $chr = 0b00000;
        $b = 0;
        while ($error >= $prec) {
            if ($isEven) {
                $next = ($minlng + $maxlng) / 2;
                if ($lng > $next) {
                    $chr |= self::$bits[$b];
                    $minlng = $next;
                } else {
                    $maxlng = $next;
                }
            } else {
                $next = ($minlat + $maxlat) / 2;
                if ($lat > $next) {
                    $chr |= self::$bits[$b];
                    $minlat = $next;
                } else {
                    $maxlat = $next;
                }
            }
            $isEven = !$isEven;
            if ($b < 4) {
                $b++;
            } else {
                $hash[] = self::$table[$chr];
                $error = max($maxlng - $minlng, $maxlat - $minlat);
                $b = 0;
                $chr = 0b00000;
            }
        }
        return join('', $hash);
    }

    public static function expand($hash, $prec = 0.00001)
    {
        list($minlng, $maxlng, $minlat, $maxlat) = self::decode($hash);
        $dlng = ($maxlng - $minlng) / 2;
        $dlat = ($maxlat - $minlat) / 2;
        return array(
            self::encode($minlng - $dlng, $maxlat + $dlat, $prec),
            self::encode($minlng + $dlng, $maxlat + $dlat, $prec),
            self::encode($maxlng + $dlng, $maxlat + $dlat, $prec),
            self::encode($minlng - $dlng, $maxlat - $dlat, $prec),
            self::encode($maxlng + $dlng, $maxlat - $dlat, $prec),
            self::encode($minlng - $dlng, $minlat - $dlat, $prec),
            self::encode($minlng + $dlng, $minlat - $dlat, $prec),
            self::encode($maxlng + $dlng, $minlat - $dlat, $prec),
        );
    }

    public static function getRect($hash)
    {
        list($minlng, $maxlng, $minlat, $maxlat) = self::decode($hash);
        return array(
            array($minlng, $minlat),
            array($minlng, $maxlat),
            array($maxlng, $maxlat),
            array($maxlng, $minlat),
        );
    }

    public static function decode($hash)
    {
        $minlng = -180;
        $maxlng = 180;
        $minlat = -90;
        $maxlat = 90;
        for ($i=0,$c=strlen($hash); $i<$c; $i++) {
            $v = strpos(self::$table, $hash[$i]);
            if (1&$i) {
                if (16&$v) {
                    $minlat = ($minlat + $maxlat) / 2;
                } else {
                    $maxlat = ($minlat + $maxlat) / 2;
                }
                if (8&$v) {
                    $minlng = ($minlng + $maxlng) / 2;
                } else {
                    $maxlng = ($minlng + $maxlng) / 2;
                }
                if (4&$v) {
                    $minlat = ($minlat + $maxlat) / 2;
                } else {
                    $maxlat = ($minlat + $maxlat) / 2;
                }
                if (2&$v) {
                    $minlng = ($minlng + $maxlng) / 2;
                } else {
                    $maxlng = ($minlng + $maxlng) / 2;
                }
                if (1&$v) {
                    $minlat = ($minlat + $maxlat) / 2;
                } else {
                    $maxlat = ($minlat + $maxlat) / 2;
                }
            } else {
                if (16&$v) {
                    $minlng = ($minlng + $maxlng) / 2;
                } else {
                    $maxlng = ($minlng + $maxlng) / 2;
                }
                if (8&$v) {
                    $minlat = ($minlat + $maxlat) / 2;
                } else {
                    $maxlat = ($minlat + $maxlat) / 2;
                }
                if (4&$v) {
                    $minlng = ($minlng + $maxlng) / 2;
                } else {
                    $maxlng = ($minlng + $maxlng) / 2;
                }
                if (2&$v) {
                    $minlat = ($minlat + $maxlat) / 2;
                } else {
                    $maxlat = ($minlat + $maxlat) / 2;
                }
                if (1&$v) {
                    $minlng = ($minlng + $maxlng) / 2;
                } else {
                    $maxlng = ($minlng + $maxlng) / 2;
                }
            }
        }
        return array($minlng, $maxlng, $minlat, $maxlat);
    }

    //存储city的GeoHash信息
    public function cacheGeoHash()
    {
        $where['type'] = 'CITY';
        $where['deleted'] = 0;
        $cityArr = Db::name("city")->where($where)->select();

        $hashArr = array();
        foreach ($cityArr as $k=>$v) {
            $lat = $v['lat'];
            $lng = $v['lng'];
            if($lat!=='' && $lng!==''){
                $hash = $this->encode($lat, $lng);
                $hashArr[] = array('id'=>$v['id'],'hash'=>$hash);
            }
        }

        cache("city_geocode",$hashArr);

        return $hashArr;
    }

    //经纬度获取城市
    public function getCity($lat, $lng)
    {
        $hash = $this->encode($lat, $lng);

        //获取城市hash集
        $hashArr = cache("city_geocode");
        if(empty($hashArr)){
            $hashArr = $this->cacheGeoHash();
        }

        if(!empty($hashArr)){
            $resArr = array();
            foreach ($hashArr as $k=>$v) {
                if($k==0){
                    $dataArr = array(
                        "level"=>0,
                        "id"=>$v['id']
                    );
                }else{
                    $dataArr = array(
                        "level"=>$resArr['level'],
                        "id"=>$resArr['id']
                    );
                }

                $resArr = $this->compareHash($hash,$v['hash'],$dataArr,$v['id']);
            }

            if($resArr['level']==0){
                return false;
            }else{
                $city = Db::name("city")->where("id",$resArr['id'])->field("id as city_id,eng_name,name")->find();
                return $city;
            }
        }else{
            return false;
        }
    }

    //比较GeoHash字符串
    public function compareHash($hash,$city_hash,$arr,$id)
    {
        $level = 0;

        //先验证前三位
        $s_first = substr($hash,0,3);
        $city_s_first = substr($city_hash,0,3);
        if($s_first==$city_s_first){
            $level = 3;
            for ($i=3;$i<strlen($hash);$i++){
                $s = substr($hash,$i,1);
                $city_s = substr($city_hash,$i,1);
                if($s==$city_s){
                    $level++;
                }else{
                    break;
                }
            }

            if($level > $arr['level']){
                $arr['level'] = $level;
                $arr['id'] = $id;
            }
        }

        return $arr;
    }

    //计算景点距离
    function getDistance($lat1, $lng1, $lat2, $lng2){
        $earthRadius = 6367000; //approximate radius of earth in meters
        $lat1 = ($lat1 * pi() ) / 180;
        $lng1 = ($lng1 * pi() ) / 180;
        $lat2 = ($lat2 * pi() ) / 180;
        $lng2 = ($lng2 * pi() ) / 180;
        $calcLongitude = $lng2 - $lng1;
        $calcLatitude = $lat2 - $lat1;
        $stepOne = pow(sin($calcLatitude / 2), 2) + cos($lat1) * cos($lat2) * pow(sin($calcLongitude / 2), 2);
        $stepTwo = 2 * asin(min(1, sqrt($stepOne)));
        $calculatedDistance = $earthRadius * $stepTwo;
        return round($calculatedDistance);
    }






}


